﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Design;
using System.Windows.Forms;
using System.ComponentModel;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;

namespace ImageEffect.Effects
{
    [TypeConverter(typeof(ExpandableObjectConverter))]
    public abstract class Effect
    {
        private string _name;
        public string GetName() { return _name; }

        protected Point _offset = new Point();
        public Point Offset { get { return _offset; } set { _offset = value; } }

        static public string[] EffectsList =
        {
            "Noise",
            "SineEffect",
            "Blur",
            "BoxEffect",
            "FillEffect",
            "Scanlines",
            //"DiagonalScanlines",
            //"HorizontalScanlines",
            //"VerticalScanlines",
            "LinearGradient",
            //"HorizontalGradient",
            //"VerticalGradient",
            "RadialGradient"
        };

        protected Effect(string name) { this._name = name; }

        public virtual void Apply(Bitmap bmp)
        {
        }

        public override string ToString()
        {
            return "";
        }
    }

    // Operate on the raw bits
    public abstract class RawEffect : Effect
    {
        public RawEffect(string name):
            base(name)
        {
        }

        protected virtual void Apply(byte[] bmpData, int width, int height)
        {
        }

        public override void Apply(Bitmap bmp)
        {
            BitmapData bmpData = bmp.LockBits(
                new Rectangle(0,0,bmp.Width, bmp.Height),
                ImageLockMode.ReadWrite,
                PixelFormat.Format32bppArgb
            );

            int bmpSize = bmpData.Stride * bmpData.Height;
            byte[] bmpBits = new byte[bmpSize];

            Marshal.Copy(bmpData.Scan0, bmpBits, 0, bmpSize);

            Apply(bmpBits, bmpData.Width, bmpData.Height);

            Marshal.Copy(bmpBits, 0, bmpData.Scan0, bmpSize);
            bmp.UnlockBits(bmpData);
        }
    }
    
    /*
     * Overlay effects maintain internal bitmaps to keep from having to recreate an effect each time
     * we change any of the other effects.  This eliminates a lot of the wait times from creating
     * more complex effects
     */
    public abstract class OverlayEffect : Effect
    {
        protected bool bChanged = true;
        protected Bitmap Bitmap { get; set; }

        protected OverlayEffect(string name) : base(name) { }

        protected virtual void Create(int width, int height)
        {
            this.Bitmap = new Bitmap(width, height);
            Graphics.FromImage(this.Bitmap).Clear(Color.FromArgb(0,0,0,0));
        }

        public override void Apply(Bitmap bmp)
        {
            if (bChanged || Bitmap == null || bmp.Width != Bitmap.Width || bmp.Height != Bitmap.Height)
            {
                this.Create(bmp.Width, bmp.Height);
            }

            if (Bitmap != null)
            {
                Graphics.FromImage(bmp).DrawImage(Bitmap, new Point(0, 0));
            }

            bChanged = false;
        }
    }

    public sealed class ImageImport : OverlayEffect
    {
        private int _alpha = 100;
        public int Alpha
        {
            get { return _alpha; }
            set { _alpha = value; }
        }

        private Bitmap _image;

        public ImageImport(Bitmap img) :
            base("Image")
        {
            _image = img;
        }

        protected override void Create(int width, int height)
        {
            Bitmap = new Bitmap(width, height);

            Graphics.FromImage(Bitmap).Clear(Color.FromArgb(0, 0, 0, 0));
            Graphics.FromImage(Bitmap).DrawImage(_image, 0, 0, width, height);
        }
    }

    public sealed class LinearGradient : OverlayEffect
    {
        private int _angle = 0;
        private Color startColor = Color.FromArgb(255, 255, 255, 255);
        private Color enColorolor = Color.FromArgb(255, 0, 0, 0);

        [DisplayNameAttribute("Begin Color"),
         Editor(typeof(ColorTypeEditor), typeof(UITypeEditor))]
        public Color StartColor { get { return startColor; } set { startColor = value; bChanged = true; } }

        [DisplayNameAttribute("End Color"),
         Editor(typeof(ColorTypeEditor), typeof(UITypeEditor))]
        public Color EndColor { get { return enColorolor; } set { enColorolor = value; bChanged = true; } }

        public int Angle
        {
            get { return _angle; }
            set { _angle = value; bChanged = true; }
        }

        public LinearGradient()
            : base("Linear Gradient")
        {
        }

        protected override void Create(int width, int height)
        {
            base.Create(width, height);

            int sideLength = (int)(Math.Sqrt(width * width + height * height));

            Bitmap tmpBmp = new Bitmap(sideLength, sideLength);
            Graphics gfx = Graphics.FromImage(tmpBmp);

            // Draw the gradient
            for (int x = 0; x < tmpBmp.Width; ++x)
            {
                Color color = Util.Interpolate(this.StartColor, this.EndColor, (double)x / (double)tmpBmp.Width);
                gfx.DrawLine(new Pen(color), new Point(x, 0), new Point(x, tmpBmp.Height));
            }

            // Rotate our canvas
            gfx = Graphics.FromImage(this.Bitmap);
            gfx.ResetTransform();
            gfx.TranslateTransform((float)width / 2, (float)height / 2);
            gfx.RotateTransform((float )this.Angle);
            gfx.TranslateTransform(-(float)sideLength / 2, -(float)sideLength / 2);

            // Apply the bitmap to our image
            gfx.DrawImage(
                tmpBmp,
                new Point(0, 0)
            );
        }
    }

    public sealed class HorizontalGradient : OverlayEffect
    {
        private Color startColor = Color.FromArgb(255,255,255,255);
        private Color enColorolor   = Color.FromArgb(255,0,0,0);

        [DisplayNameAttribute("Begin Color"),
         Editor(typeof(ColorTypeEditor), typeof(UITypeEditor))]
        public Color StartColor { get { return startColor; } set { startColor = value; bChanged = true;  } }

        [DisplayNameAttribute("End Color"),
         Editor(typeof(ColorTypeEditor), typeof(UITypeEditor))]
        public Color EndColor { get { return enColorolor; } set { enColorolor = value; bChanged = true; } }

        public HorizontalGradient(): base("Horizontal Gradient")
        {
        }

        protected override void Create(int width, int height)
        {
            Bitmap = new Bitmap(width, height);

            for (int x = 0; x < width; ++x)
            {
                Color color = Util.Interpolate(this.StartColor, this.EndColor, (double)x / (double)width);
                Graphics.FromImage(Bitmap).DrawLine(new Pen(color), new Point(x, 0), new Point(x, height));
            }
        }
    }

    public sealed class VerticalGradient : OverlayEffect
    {
        private Color startColor = Color.FromArgb(255, 255, 255, 255);
        private Color endColor = Color.FromArgb(255, 0, 0, 0);

        [DisplayNameAttribute("Begin Color"),
         Editor(typeof(ColorTypeEditor), typeof(UITypeEditor))]
        public Color StartColor { get { return startColor; } set { startColor = value; bChanged = true; } }

        [DisplayNameAttribute("End Color"),
         Editor(typeof(ColorTypeEditor), typeof(UITypeEditor))]
        public Color EndColor { get { return endColor; } set { endColor = value; bChanged = true; } }

        public VerticalGradient()
            : base("Vertical Gradient")
        {
        }

        protected override void Create(int width, int height)
        {
            base.Create(width, height);

            for (int y = 0; y < height; ++y)
            {
                Color color = Util.Interpolate(this.StartColor, this.EndColor, (double)y / (double)height);
                Graphics.FromImage(Bitmap).DrawLine(new Pen(color), new Point(0, y), new Point(width, y));
            }
        }
    }

    public sealed class RadialGradient : OverlayEffect
    {
        private Color _startColor = Color.White;
        private Color _endColor   = Color.Black;
        private Point _center = new Point(-1, -1);

        [DisplayNameAttribute("Begin Color"),
         Editor(typeof(ColorTypeEditor), typeof(UITypeEditor))]
        public Color StartColor { get { return _startColor; } set { _startColor = value; bChanged = true; } }

        [DisplayNameAttribute("End Color"),
         Editor(typeof(ColorTypeEditor), typeof(UITypeEditor))]
        public Color EndColor { get { return _endColor; } set { _endColor = value; bChanged = true; } }

        public Point Center { get { return _center; } set { _center = value; bChanged = true; } }

        public RadialGradient()
            : base("Radial Gradient")
        {
        }

        protected override void Create(int width, int height)
        {
            base.Create(width, height);

            Util.OnRawData(this.Bitmap,
                delegate(byte[] bmpData, int w, int h)
                {
                    int cx = this.Center.X;
                    int cy = this.Center.Y;

                    if (cx == -1)
                        cx = width / 2;
                    if (cy == -1)
                        cy = height / 2;

                    int d2x = cx - width;
                    int d2y = cy - height;

                    int offset = 0;
                    for (int y = 0; y < height; ++y)
                    {
                        for (int x = 0; x < width; ++x)
                        {
                            int d1x = cx - x;
                            int d1y = cy - y;

                            int d1 = (int)Math.Sqrt(d1x * d1x + d1y * d1y);
                            int d2 = (int)Math.Sqrt(d2x * d2x + d2y * d2y);

                            Color c = Util.Interpolate(this.StartColor, this.EndColor, (double)d1 / (double)d2);
                            bmpData[offset++] = c.B;
                            bmpData[offset++] = c.G;
                            bmpData[offset++] = c.R;
                            bmpData[offset++] = c.A;
                        }
                    }
                }
            );
        }
    }

    public sealed class FillEffect : OverlayEffect
    {
        private Color _color = Color.White;

        [DisplayNameAttribute("Color"),
         Editor(typeof(ColorTypeEditor), typeof(UITypeEditor))]
        public Color Color { get { return _color; } set { _color = value; bChanged = true; } }

        public FillEffect()
            : base("Fill")
        {
        }

        protected override void  Create(int width, int height)
        {
            base.Create(width, height);

            Graphics.FromImage(this.Bitmap).Clear(this.Color);
        }
    }

    public sealed class BoxEffect : RawEffect
    {
        [DisplayNameAttribute("Max Opacity")]
        public int MaxOpacity { get; set; }

        [DisplayNameAttribute("Side Length")]
        public int SideLength { get; set; }

        public BoxEffect()
            : base("Boxes")
        {
            MaxOpacity = 50;
            SideLength = 30;
        }

        protected override void Apply(byte[] bmpData, int width, int height)
        {
            if (SideLength == 0) SideLength = 1;

            double maxOpacity = (double)this.MaxOpacity / 100.0f;
            Random random = new Random(this.SideLength * (int)(100.0 * maxOpacity));
            for (int y = 0; y < height; y += this.SideLength)
            {
                int yOffset = y * 4 * width;

                // Draw the first line
                for (int x = 0; x < width * 4; x += this.SideLength * 4)
                {
                    double boxOpacity = random.NextDouble();

                    Color c = Util.Interpolate(
                        Color.FromArgb(
                            bmpData[yOffset + x + 3],
                            bmpData[yOffset + x + 0],
                            bmpData[yOffset + x + 1],
                            bmpData[yOffset + x + 2]
                        ),
                        Color.White,
                        boxOpacity * maxOpacity
                    );

                    // Draw our line segment
                    for (int x2 = 0; x2 < this.SideLength * 4; x2 += 4)
                    {
                        bmpData[yOffset + x + x2 + 0] = c.R;
                        bmpData[yOffset + x + x2 + 1] = c.G;
                        bmpData[yOffset + x + x2 + 2] = c.B;
                        bmpData[yOffset + x + x2 + 3] = c.A;
                    }
                }

                // Duplicate the top line to make our rectangles
                for (int offset = yOffset + width*4; offset < yOffset + (width * 4 * this.SideLength) && offset < width * 4 * height; ++offset)
                {
                    bmpData[offset] = bmpData[offset - width * 4];
                }
            }
/*
            for (int x = 0; x < bmp.Width; x += this.SideLength)
            {
                for (int y = 0; y < bmp.Height; y += this.SideLength)
                {
                    double boxOpacity = random.NextDouble();

                    Color c = bmp.GetPixel(x, y);
                    Graphics.FromImage(bmp).FillRectangle(
                        new SolidBrush(Util.Interpolate(c, Color.White, boxOpacity * maxOpacity)),
                        new Rectangle(x, y, this.SideLength, this.SideLength)
                    );
                }
            }
 */
        }
    }

    public sealed class Blur: RawEffect
    {
        public int Iterations { get; set; }

        public Blur()
            : base("Blur")
        {
            // default is gaussian
            this.Iterations = 3;
        }

        protected override void Apply(byte[] bmpData, int width, int height)
        {
            // First, create an array of floats
            float[,] rValues = new float[width, height];
            float[,] gValues = new float[width, height];
            float[,] bValues = new float[width, height];

            // Fill the arrays with our values
            for (int y = 0; y < height; y++)
            {
                int offset = y * width * 4;
                for (int x = 0; x < width; x++)
                {
                    rValues[x, y] = (float)bmpData[offset + x * 4 + 2];
                    gValues[x, y] = (float)bmpData[offset + x * 4 + 1];
                    bValues[x, y] = (float)bmpData[offset + x * 4 + 0];
                }
            }

            // blur the number of desired time (3 is equivelant to gaussian)
            for (int i = 0; i < this.Iterations; i++)
            {
                rValues = BoxBlur.Blur(rValues, width, height);
                gValues = BoxBlur.Blur(gValues, width, height);
                bValues = BoxBlur.Blur(bValues, width, height);
            }

            // Write all of the new values
            for (int y = 0; y < height; y++)
            {
                int offset = y * width * 4;
                for (int x = 0; x < width; x++)
                {
                    bmpData[offset + x * 4 + 2] = (byte )rValues[x, y];
                    bmpData[offset + x * 4 + 1] = (byte )gValues[x, y];
                    bmpData[offset + x * 4 + 0] = (byte )bValues[x, y];
                }
            }
        }
    }

    public class Noise : RawEffect
    {
        public Noise()
            : base("Noise")
        {
        }

        protected override void Apply(byte[] bmpData, int width, int height)
        {
            base.Apply(bmpData, width, height);
        }
    }

    public class SineEffect : RawEffect
    {
        public int Amplitude { get; set; }
        public int Wavelength { get; set; }

        public SineEffect()
            : base("Sine")
        {
            Amplitude = 10;
            Wavelength = 10;
        }

        protected override void Apply(byte[] bmpData, int width, int height)
        {
            byte[] dest = new byte[width * 4 * height];

            int[] heights = new int[width];
            for (int x = 0; x < width; x++)
                heights[x] = (int)(Amplitude * Math.Sin((double)(x+Offset.X) / (double)this.Wavelength));

            int idx = 0;
            int size = width * height * 4;
            for (int y = 0; y < height; ++y)
            {
                for (int x = 0; x < width; ++x)
                {
                    int offset = (y + heights[x]) * width * 4 + x * 4;
                    
                    while (offset < 0)
                        offset += width * 4;

                    while (offset >= size)
                        offset -= width * 4;

                    dest[idx + 0] = bmpData[offset + 0];
                    dest[idx + 1] = bmpData[offset + 1];
                    dest[idx + 2] = bmpData[offset + 2];
                    dest[idx + 3] = bmpData[offset + 3];
                
                    idx += 4;
                }
            }

            dest.CopyTo(bmpData, 0);
        }
    }

#if false
    /*
     *  The Majority of these remaining effects are commented out
     *  because they are too slow.  The rest are commented out because
     *  there isn't enough demand for them
     */

    public abstract class LinearGradientEffect : Effect
    {
        protected readonly Color top;
        protected readonly Color bottom;

        public LinearGradientEffect(Color top, Color bottom)
            : base()
        {
            this.top = top;
            this.bottom = bottom;
        }
    }

    public sealed class LynyrdGradientEffect : Effect
    {
        private readonly Color color;

        private readonly double opacity;

        private int centerX;
        private int centerY;

        public LynyrdGradientEffect(Color color, double opacity)
            : this(color, opacity, -1, -1)
        {
        }

        public LynyrdGradientEffect(Color color, double opacity, int centerX, int centerY)
        {
            this.color = color;

            this.opacity = opacity;

            this.centerX = centerX;
            this.centerY = centerY;
        }

        public override void Apply(Bitmap bmp)
        {
            if (this.centerX == -1 && this.centerY == -1)
            {
                this.centerX = bmp.Width / 2;
                this.centerY = bmp.Height / 2;
            }

            for (int x = 0; x < bmp.Width; ++x)
            {
                for (int y = 0; y < bmp.Height; ++y)
                {
                    int d1x = this.centerX - x;
                    int d1y = this.centerY - y;

                    int d2x = this.centerX - bmp.Width;
                    int d2y = this.centerY - bmp.Height;

                    int d1 = (int)Math.Sqrt(d1x * d1x + d1y * d1y);
                    int d2 = (int)Math.Sqrt(d2x * d2x + d2y * d2y);

                    bmp.SetPixel(x, y,
                        color.Interpolate(bmp.GetPixel(x, y), ((double)d1 / (double)d2) * opacity));
                }
            }
        }
    }
#endif
}
